/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkConditionExpr, BinaryOperator, GlobalRef, UnionType } from 'arkanalyzer/lib';
import { BaseChecker, BaseMetaData } from '../BaseChecker';
import { Defects, IssueReport } from '../../model/Defects';
import { MatcherCallback, MatcherTypes, StmtMatcher } from '../../matcher/Matchers';
import {
    ArkAssignStmt,
    ArkInstanceInvokeExpr,
    ArkNewExpr,
    ArkNormalBinopExpr,
    ArkUnopExpr,
    Constant,
    LineColPosition,
    Local,
    Stmt,
    Value
} from 'arkanalyzer';
import { UnaryOperator } from 'arkanalyzer/lib/core/base/Expr';
import { Rule } from '../../model/Rule';
import { RuleListUtil } from '../../utils/common/DefectsList';
import { RuleFix } from '../../model/Fix';
import { NumberConstant } from 'arkanalyzer/lib/core/base/Constant';
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';

interface Issue {
    ruleFix: RuleFix | null;
    line: number;
    column: number;
    message: string;
    filePath: string;
}

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'PreferIncludesCheck');
const signatureStrs: string[] = [
    '@built-in/lib.es5.d.ts: RegExp.test(string)',
    '@built-in/lib.es5.d.ts: String.indexOf(string, number)',
    '@built-in/lib.es5.d.ts: Array.indexOf(T, number)',
    '@built-in/lib.es5.d.ts: ReadonlyArray.indexOf(T, number)',
    '@built-in/lib.es5.d.ts: Int8Array.indexOf(number, number)',
    '@built-in/lib.es5.d.ts: Int16Array.indexOf(number, number)',
    '@built-in/lib.es5.d.ts: Int32Array.indexOf(number, number)',
    '@built-in/lib.es5.d.ts: Uint8Array.indexOf(number, number)',
    '@built-in/lib.es5.d.ts: Uint16Array.indexOf(number, number)',
    '@built-in/lib.es5.d.ts: Uint32Array.indexOf(number, number)',
    '@built-in/lib.es5.d.ts: Float32Array.indexOf(number, number)',
    '@built-in/lib.es5.d.ts: Float64Array.indexOf(number, number)'
];

const RegExpConstructor: string[] = [
    '@built-in/lib.es5.d.ts: RegExpConstructor.construct-signature(@built-in/lib.es5.d.ts: RegExp|string)',
    '@built-in/lib.es5.d.ts: RegExpConstructor.create(@built-in/lib.es5.d.ts: RegExp|string)'
];

export class PreferIncludesCheck implements BaseChecker {
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private REGEX_EXTRACT_PATTERN = /^\/(.+?)\/([gimyus]*)$/;

    public metaData: BaseMetaData = {
        description: 'Enforce includes method over indexOf method.',
        fixable: true,
        severity: 1,
        ruleDocPath: 'docs/prefer-includes.md'
    };

    private stmtMatcher: StmtMatcher = {
        matcherType: MatcherTypes.STMT,
        match(stmt: Stmt): boolean {
            const signature = stmt.getInvokeExpr()?.getMethodSignature().toString() || '';
            return signatureStrs.includes(signature);
        }
    };

    constructor() {
    }

    registerMatchers(): MatcherCallback[] {
        const stmtMatcherCb: MatcherCallback = {
            matcher: this.stmtMatcher,
            callback: this.check
        };
        return [stmtMatcherCb];
    }

    // 检查方法：只负责判断语句类型，调用抽取的逻辑决定是否处理
    check = (stmt: Stmt): void => {
        const signature = stmt.getInvokeExpr()?.getMethodSignature().toString();
        const testStatus = signature === signatureStrs[0];
        if (testStatus) {//test的方法调用
            this.checkTestPreferIncludes(stmt);
        } else { //indexOf的方法调用
            const issues = this.checkIndexOfPreferIncludes(stmt);
            for (const issue of issues) {
                issue.filePath = stmt.getCfg().getDeclaringMethod().getDeclaringArkFile().getFilePath();
                this.addIssueReport(issue);
            }
        }
    };

    private checkIndexOfPreferIncludes(stmt: Stmt): Issue[] {
        let fixStatus = true;//判断？是否需要告警不修复
        const issues: Issue[] = [];
        const invokeExpr = stmt.getInvokeExpr();
        if (invokeExpr instanceof ArkInstanceInvokeExpr) {
            if (this.isPositiveIndexOfCase(stmt, invokeExpr)) {
                return issues; // 正例直接跳出1-3，4-7
            }
            let base = invokeExpr.getBase().getName();
            const args = invokeExpr.getArgs();
            const parameters = stmt.getCfg().getDeclaringMethod().getSubSignature().getParameters();
            parameters.forEach((parameter) => {
                fixStatus = !parameter.isOptional();
            });
            this.addIssueForCheck(stmt, base, args, fixStatus);
        }
        return issues;
    }

    private checkTestPreferIncludes(stmt: Stmt): void {
        const issues: Issue[] = [];
        const invokeExpr = stmt.getInvokeExpr();
        if (!(invokeExpr instanceof ArkInstanceInvokeExpr)) {
            return;
        }
        if (invokeExpr.getArgs().length !== 1) {
            return;
        }
        const base = invokeExpr.getBase();
        let regExpStr = this.getRegExpStr(base);
        if (!regExpStr) {
            let global = stmt.getCfg().getDeclaringMethod().getBody()?.getUsedGlobals()?.get(base.getName());
            if (global instanceof GlobalRef && global.getRef() instanceof Local) {
                regExpStr = this.getRegExpStr(global.getRef() as Local);
            }
        }
        if (!regExpStr) {
            return;
        }
        let reg = this.getRegExpByPattern(regExpStr);
        if (!reg) {
            return;
        }
        // 仅允许纯文本（无正则元字符）
        const onlyPlain = /^[^.*+?^${}()|[$\\\/]+$/.test(reg.source);
        if (onlyPlain && reg.flags === '') {
            const ret = this.processInvokeArgs(invokeExpr);
            this.addTestIssueForCheck(stmt, reg.source, ret.processedArgs, ret.needBracket);
        }
    }

    private getRegExpByPattern(pattern: string): RegExp | null {
        let reg: RegExp | null = null;
        let regStr;
        let flags;
        // 判断是否为像 /bar/i 这样的正则字面量字符串
        if (pattern.startsWith('/') && pattern.lastIndexOf('/') > 0) {
            const lastSlash = pattern.lastIndexOf('/');
            regStr = pattern.slice(1, lastSlash);
            flags = pattern.slice(lastSlash + 1);
        } else {
            regStr = pattern;
        }
        try {
            reg = new RegExp(regStr, flags);
        } catch (e) {
            logger.warn(`Invalid regexp pattern: ${pattern}`);
        }
        return reg;
    }


    private addIssueForCheck(stmt: Stmt, base: string, args: Value[], fixStatus: boolean): void {
        const loc = stmt.getOriginPositionInfo();
        if (fixStatus) {
            const ruleFix = this.createIndexOfToIncludesFix(base, args[0].toString(),
                loc, stmt, this.exclamationPointStatus(stmt)); //在这里判断！是否需要增加然后传入修复方式
            if (ruleFix) {
                const issue: Issue = {
                    ruleFix,
                    line: stmt.getOriginPositionInfo().getLineNo(),
                    column: stmt.getOriginPositionInfo().getColNo(),
                    message: this.metaData.description,
                    filePath: stmt.getCfg().getDeclaringMethod().getDeclaringArkFile().getFilePath()
                };
                this.addIssueReport(issue);
            }
        } else {
            //只告警不修复
            const issue: Issue = {
                ruleFix: null,
                line: stmt.getOriginPositionInfo().getLineNo(),
                column: stmt.getOriginPositionInfo().getColNo(),
                message: this.metaData.description,
                filePath: stmt.getCfg().getDeclaringMethod().getDeclaringArkFile().getFilePath()
            };
            this.addIssueReport(issue);
        }
    }

    private addTestIssueForCheck(stmt: Stmt, base: string, args: string, bracketStatus: boolean): void {
        const loc = stmt.getOriginPositionInfo();
        const ruleFix = this.createTestToIncludesFix(base, args, loc, stmt, bracketStatus);
        if (ruleFix) {
            const issue: Issue = {
                ruleFix,
                line: stmt.getOriginPositionInfo().getLineNo(),
                column: stmt.getOriginPositionInfo().getColNo(),
                message: this.metaData.description,
                filePath: stmt.getCfg().getDeclaringMethod().getDeclaringArkFile().getFilePath()
            };
            this.addIssueReport(issue);
        }
    }

    private createIndexOfToIncludesFix(base: string,
                                       arg: string,
                                       loc: LineColPosition,
                                       stmt: Stmt,
                                       exclamationPointStatus: boolean):
        RuleFix | null {
        let replacementText: string | undefined = '';
        const startColumn = loc.getColNo();
        const endColumn = loc.getColNo() + (stmt.getOriginalText()?.length ?? 0);
        if (exclamationPointStatus) {
            replacementText += '!';
        }
        replacementText += base;
        replacementText += '.';
        replacementText += 'includes(';
        replacementText += arg;
        replacementText += ');';
        if (startColumn === -1 || endColumn === -1) {
            return null;
        }
        return {
            range: [startColumn, endColumn],
            text: replacementText
        };
    }

    private createTestToIncludesFix(base: string,
                                    arg: string,
                                    loc: LineColPosition,
                                    stmt: Stmt,
                                    bracketStatus: boolean):
        RuleFix | null {
        let replacementText: string | undefined = '';
        const startColumn = loc.getColNo();
        const endColumn = loc.getColNo() + (stmt.getOriginalText()?.length ?? 0);
        if (bracketStatus) {
            replacementText += '(';
            replacementText += arg;
            replacementText += ')';
        } else {
            replacementText += arg;
        }
        replacementText += '.';
        replacementText += 'includes(\'';
        replacementText += base;
        replacementText += '\');';
        if (startColumn === -1 || endColumn === -1) {
            return null;
        }
        return {
            range: [startColumn, endColumn],
            text: replacementText
        };
    }

    private addIssueReport(issue: Issue): void {
        this.metaData.description = issue.message;
        const severity = this.rule.alert ?? this.metaData.severity;
        const startColumn = issue.ruleFix?.range?.[0] ?? issue.column;
        const endColumn = issue.ruleFix?.range?.[1] ?? issue.column;
        const defect = new Defects(issue.line, startColumn,
            endColumn, this.metaData.description, severity,
            this.rule.ruleId, issue.filePath, this.metaData.ruleDocPath,
            true, false, true);
        RuleListUtil.push(defect);
        let issueReport: IssueReport = {
            defect,
            fix: issue.ruleFix || undefined
        };
        this.issues.push(issueReport);
    }

    private exclamationPointStatus(stmt: Stmt): boolean {
        if (!(stmt instanceof ArkAssignStmt)) {
            return false;
        }
        const leftOp = stmt.getLeftOp();
        const rightOp = stmt.getRightOp();
        if (!(rightOp instanceof ArkConditionExpr && leftOp instanceof Local)) {
            return false;
        }
        const operator = rightOp.getOperator();
        const op1 = rightOp.getOp1();
        const op2 = rightOp.getOp2();
        return (this.isPositiveCheck(operator, op2) ||
            this.isNegativeCheck(operator, op2)) && op1 instanceof Local;
    }

    private isPositiveIndexOfCase(stmt: Stmt, invokeExpr: ArkInstanceInvokeExpr): boolean {
        // 正例1：仅调用 indexOf 未与 -1 比较（无比较运算符）
        if (!this.hasMinusOneComparison(stmt)) {
            return true;
        }
        // 正例3：联合类型中得看参数是否能使用indexOf,正例表示为’‘
        const baseType = invokeExpr.getBase().getType();
        let isUnionType = false;
        if (baseType instanceof UnionType) {
            isUnionType = true;
        }
        if (isUnionType && stmt instanceof ArkAssignStmt) {
            const rightOp = stmt.getRightOp();
            if (rightOp instanceof ArkInstanceInvokeExpr) {
                const className = rightOp.getMethodSignature().getDeclaringClassSignature().getClassName();
                if (className === 'String') {
                    return true;
                }
            }
        }
        //正例4-7用例屏蔽
        // 定义标准includes方法的签名结构
        return false;
    }

    private hasMinusOneComparison(stmt: Stmt): boolean {
        let status = false;
        if (!(stmt instanceof ArkAssignStmt)) {
            return status; // 非赋值语句，排除
        }
        //找到的stmt是最外层的，得往里面继续算
        const leftOp1 = stmt.getLeftOp();
        if (!(leftOp1 instanceof Local)) {
            return status;
        }
        const usedStmts = leftOp1.getUsedStmts();
        usedStmts.forEach((useStmt: Stmt) => {
            if (useStmt instanceof ArkAssignStmt) {
                const rightOp = useStmt.getRightOp();
                const leftOp = useStmt.getLeftOp();
                // 正例2：indexOf 结果用于算术运算（+、-等）
                if (rightOp instanceof ArkNormalBinopExpr) {
                    return status;
                }
                if (rightOp instanceof ArkConditionExpr && leftOp instanceof Local) {
                    const operator = rightOp.getOperator();
                    const op1 = rightOp.getOp1();
                    const op2 = rightOp.getOp2();
                    // 4. 确认比较的右操作数是-1
                    status = this.isNumber(op2, -1) || this.isNumber(op2, 0);
                    return status;
                }
            }
            return status;
        });

        return status;
    }

    isNumber(op2: Value, value: number): boolean {
        if (op2 instanceof NumberConstant) {
            return parseInt(op2.getValue()) === value;
        }

        if (!(op2 instanceof Local)) {
            return false;
        }

        const declaringStmt = op2.getDeclaringStmt();
        if (!(declaringStmt instanceof ArkAssignStmt)) {
            return false;
        }

        const rightOp = declaringStmt.getRightOp();
        if (!(rightOp instanceof ArkUnopExpr)) {
            return false;
        }

        const op = rightOp.getOp();
        if (!(op instanceof NumberConstant) || rightOp.getOperator() !== UnaryOperator.Neg) {
            return false;
        }

        return parseInt(`${rightOp.getOperator()}${op.getValue()}`) === value;
    }

    isPositiveCheck(operator: BinaryOperator, op2: Value): boolean {
        switch (operator) {
            case '!==':
            case '!=':
            case '>':
                return this.isNumber(op2, -1);
            case '>=':
                return this.isNumber(op2, 0);
            default:
                return false;
        }
    }

    isNegativeCheck(operator: BinaryOperator, op2: Value): boolean {
        switch (operator) {
            case '===':
            case '==':
            case '<=':
                return this.isNumber(op2, -1);
            case '<':
                return this.isNumber(op2, 0);
            default:
                return false;
        }
    }

    extractRegExpPattern(regexText: string): { pattern: string | null; flags: string | null } {
        const match = regexText.match(this.REGEX_EXTRACT_PATTERN);
        if (match) {
            return { pattern: match[1], flags: match[2] };
        }
        return { pattern: null, flags: null };
    }

    /**
     * 处理调用表达式的参数，解析其中的二元表达式（如 a+b）
     * @param invokeExpr 实例调用表达式
     * @returns 处理后的参数字符串和是否需要括号的状态
     */
    private processInvokeArgs(invokeExpr: ArkInstanceInvokeExpr): { processedArgs: string; needBracket: boolean } {
        // 初始化参数为原始参数的字符串形式
        let processedArgs = invokeExpr.getArgs().toString();
        let needBracket = false;

        // 遍历参数，检查是否包含二元表达式
        invokeExpr.getArgs().forEach((arg) => {
            if (arg instanceof Local) {
                const declaringStmt1 = arg.getDeclaringStmt();
                if (declaringStmt1 instanceof ArkAssignStmt) {
                    const rightOp = declaringStmt1.getRightOp();
                    if (rightOp instanceof ArkNormalBinopExpr) {
                        // 拼接二元表达式的操作数和运算符
                        const op1 = rightOp.getOp1();
                        const op2 = rightOp.getOp2();
                        const operator = rightOp.getOperator();
                        processedArgs = `${op1}${operator}${op2}`;
                        needBracket = true; // 二元表达式需要括号
                    }
                }
            }
        });

        return { processedArgs, needBracket };
    }


    private getRegExpStr(local: Local): string | undefined {
        const declaringStmt = local.getDeclaringStmt();
        let result;
        if (declaringStmt instanceof ArkAssignStmt) {
            const rightOp = declaringStmt.getRightOp();
            if (rightOp instanceof Constant) {
                result = rightOp.getValue();
            } else if (rightOp instanceof ArkNewExpr) {
                const defStmt = local.getUsedStmts()
                    .find(s => RegExpConstructor.includes(s.getInvokeExpr()?.getMethodSignature().toString() || ''));
                let arg = defStmt?.getInvokeExpr()?.getArg(0);
                if (arg instanceof Constant) {
                    result = arg.getValue();
                }
            } else if (rightOp instanceof Local) {
                return this.getRegExpStr(rightOp);
            }
        }
        return result;
    }
}